#include <ap/settings/ds_settings.h>

#pragma warning(disable: 4146)
#pragma warning(disable: 4800)

#include "fileio.h"

#include "ds_algorithm.h"
#include "ds_data.h"
#include "ds_data_class_access.h"
#include "ds_data_type_impl.hpp"
#include "ds_data_std_types.hpp"
#include "ds_ref_vector.h"
#include "ds_param.h"

DEFINE_TYPE_ID(NULL_TYPE)
DEFINE_TYPE_ID(char)
DEFINE_TYPE_ID(BYTE)
DEFINE_TYPE_ID(short)
DEFINE_TYPE_ID(WORD)
DEFINE_TYPE_ID(int)
DEFINE_TYPE_ID(DWORD)
DEFINE_TYPE_ID(bool)
DEFINE_TYPE_ID(float)
DEFINE_TYPE_ID(double)
DEFINE_TYPE_ID(dsSTRING)
DEFINE_TYPE_ID(dsWSTRING)

DEFINE_TYPE_ID(INT64)

namespace ds_data {

struct TYPE_REC {
   CBUnaryOp            unaryOp[_DSD_UNARY_MAX];
   const dsDATA_TYPE   *mtSafeAnalog;
};

struct TYPE2TYPE_REC {
   CBConvert      converter;
   CBRawConvert   rawConverter;
   DWORD          convertFlags;
   CBCompare      comparator;
   CBBinaryOp     mathOp[_DSD_BINARY_MAX];
};

static TYPE2TYPE_REC t2tTable[DATA_TYPE_ID_MAX][DATA_TYPE_ID_MAX];
static TYPE_REC typeTable[DATA_TYPE_ID_MAX];
static const dsDATA_TYPE* idTable[256];

int gTypeId = 0;

static void Init(void)
{
   static BOOL isInited = FALSE;
   if (!isInited) {
      memset(t2tTable, 0, sizeof(t2tTable));
      memset(typeTable, 0, sizeof(typeTable));
      isInited = TRUE;
   }
   return;
}

void RegisterType(const dsDATA_TYPE *type)
{
   int id = type->GetId();
   STRONG_ASSERT(id >= 0 && id < gTypeId);
   STRONG_ASSERT(id < _NElem(idTable));
   idTable[id] = type;
   
   return;
}

const dsDATA_TYPE *GetTypeById(int id)
{
   STRONG_ASSERT(id >= 0 && id < gTypeId);
   return idTable[id];
}

void RegisterConverter(int typeId1, int typeId2, CBConvert converter, CBRawConvert rawConverter, DWORD flags)
{
   Init();

   STRONG_ASSERT(typeId1 >= 0 && typeId1 < DATA_TYPE_ID_MAX);
   STRONG_ASSERT(typeId2 >= 0 && typeId2 < DATA_TYPE_ID_MAX);
   STRONG_ASSERT(converter != NULL);

   if (t2tTable[typeId1][typeId2].converter != NULL) {
      //STRONG_ASSERT(t2tTable[typeId1][typeId2].converter == converter); // another converter is being registered on the same id pair
   } else {
      t2tTable[typeId1][typeId2].converter      = converter;
      t2tTable[typeId1][typeId2].rawConverter   = rawConverter;
      t2tTable[typeId1][typeId2].convertFlags   = flags;
   }

   return;
}

CBConvert GetConverter(int typeId1, int typeId2, dsCONVERTION_TYPE cvtType)
{
   if (typeId1 == 0 || typeId2 == 0) {
      return NULL;
   }

   if (!_IsState(t2tTable[typeId1][typeId2].convertFlags, 1 << cvtType)) {
      return NULL;
   }

   return t2tTable[typeId1][typeId2].converter;
}

CBRawConvert GetRawConverter(int typeId1, int typeId2, dsCONVERTION_TYPE cvtType)
{
   if (typeId1 == 0 || typeId2 == 0) {
      return NULL;
   }

   if (!_IsState(t2tTable[typeId1][typeId2].convertFlags, 1 << cvtType)) {
      return NULL;
   }

   return t2tTable[typeId1][typeId2].rawConverter;
}

void RegisterMTSafeTypeAnalog(int typeId, const dsDATA_TYPE* typeDest)
{
   Init();

   STRONG_ASSERT(typeId >= 0 && typeId < DATA_TYPE_ID_MAX);
   STRONG_ASSERT(typeDest);

   // another MT safe analogue has been already registered
   STRONG_ASSERT(typeTable[typeId].mtSafeAnalog == NULL);

   typeTable[typeId].mtSafeAnalog = typeDest;
}

const dsDATA_TYPE* GetMTSafeTypeAnalog(int typeId) 
{
   return typeTable[typeId].mtSafeAnalog;
}

void RegisterComparator(int typeId1, int typeId2, CBCompare comparator)
{
   Init();

   STRONG_ASSERT(typeId1 >= 0 && typeId1 < DATA_TYPE_ID_MAX);
   STRONG_ASSERT(typeId2 >= 0 && typeId2 < DATA_TYPE_ID_MAX);
   STRONG_ASSERT(comparator != NULL);

   if (t2tTable[typeId1][typeId2].comparator != NULL) {
      //!STRONG_ASSERT(t2tTable[typeId1][typeId2].comparator == comparator); // another comparator is being registered on the same id pair
   } else {
      t2tTable[typeId1][typeId2].comparator      = comparator;
   }

   return;  
}

CBCompare GetComparator(int typeId1, int typeId2)
{
   return t2tTable[typeId1][typeId2].comparator;
}

void RegisterBinaryOp(int op, int typeId1, int typeId2, CBBinaryOp CBOp)
{
   Init();

   STRONG_ASSERT(typeId1 >= 0 && typeId1 < DATA_TYPE_ID_MAX);
   STRONG_ASSERT(typeId2 >= 0 && typeId2 < DATA_TYPE_ID_MAX);
   STRONG_ASSERT(CBOp != NULL);

   if (t2tTable[typeId1][typeId2].mathOp[op] != NULL) {
      //STRONG_ASSERT(t2tTable[typeId1][typeId2].mathOp[op] == CBOp); // another comparator is being registered on the same id pair
   } else {
      t2tTable[typeId1][typeId2].mathOp[op]  = CBOp;
   }

   return;  
}

CBBinaryOp GetBinaryOp(int op, int typeId1, int typeId2)
{
   return t2tTable[typeId1][typeId2].mathOp[op];
}

void RegisterUnaryOp(int op, int typeId, CBUnaryOp CBOp)
{
   Init();

   STRONG_ASSERT(typeId >= 0 && typeId < DATA_TYPE_ID_MAX);
   STRONG_ASSERT(CBOp != NULL);

   if (typeTable[typeId].unaryOp[op] != NULL) {
      //STRONG_ASSERT(typeTable[typeId].unaryOp[op] == CBOp); // another comparator is being registered on the same id pair
   } else {
      typeTable[typeId].unaryOp[op]  = CBOp;
   }

   return;  
}

CBUnaryOp GetUnaryOp(int op, int typeId)
{
   return typeTable[typeId].unaryOp[op];
}

} // namespace

template<>
ds::SELF_LINKED<dsDATA_RUNTIME_INIT> * ds::SELF_LINKED<dsDATA_RUNTIME_INIT>::ms_first = 0;

// types table definition
const dsDATA_TYPE* typeDsTable[DATA_TYPE_ID_MAX];

void dsDataInit()
{
   ds::SELF_LINKED<dsDATA_RUNTIME_INIT>::Enumerate(ds::MEM_FUN(&dsDATA_RUNTIME_INIT::Init));

   _ZeroStruct(typeDsTable);

   DS_REGISTER_DS_TYPES(float);
   DS_REGISTER_DS_TYPES(int);
   DS_REGISTER_DS_TYPES(bool);
   DS_REGISTER_DS_TYPES(dsSTRING);
   DS_REGISTER_DS_TYPES(dsSTRID);
   DS_REGISTER_DS_TYPES(refVector);
   DS_REGISTER_DS_TYPES(dsVECTOR_PARAM_LIST);
}

/**************************************************************
* dsWriteDsData()
*
**************************************************************/
void dsWriteDsData (fioFILE& stream, const dsDATA& data)
{
   stream.Write(data.GetTypeId());
   if (data.GetTypeId() == DATA_TYPE_ID_UNDEF) {
   } else if (data.IsType<refVector>()) {
      const refVector* vec = data.GetPtr<refVector>();
      if (!vec) {
         stream.Write(0);
         return;
      }
      const dsREF_VECTOR* cur = vec->Get();
      if (!cur) {
         stream.Write(0);
         return;
      }
      stream.Write(cur->Length());
      for (int i = 0; i < cur->Length(); i++) {
         dsWriteDsData(stream, (*cur)[i]);
      }
   } else if (data.IsType<dsVECTOR_PARAM_LIST>()) {
      const dsVECTOR_PARAM_LIST* list = data.GetPtr<dsVECTOR_PARAM_LIST>();
      if (!list) {
         stream.Write(0);
         return;
      }
      stream.Write(list->GetStorage().Length());
      for (int i = 0; i < list->GetStorage().Length(); i++) {
         stream.Write(dsSTRING(list->GetStorage()[i].id.CStr()));
         dsWriteDsData(stream, list->GetStorage()[i].data);
      }
   } else if (data.IsType<dsSTRID>()) {
      stream.Write(dsSTRING(data.GetPtr<dsSTRID>()->CStr()));
   } else {
      data.Save(&stream);
   }
}

/**************************************************************
* dsReadDsData()
*
**************************************************************/
void dsReadDsData (fioFILE& stream, dsDATA& data)
{
   int typeId;
   stream.Read(&typeId);
   if (typeId < 0) {
      return;
   }

   const dsDATA_TYPE* pType = typeDsTable[typeId];
   if (!pType) {
      ASSERT(FALSE);
      return;
   }

   if (pType == dsDATA_TYPE::GetType<refVector>()) {
      int length;
      stream.Read(&length);
      refVector vec;
      dsREF_VECTOR* cur = vec.Get();
      cur->Resize(length);
      for (int i = 0; i < cur->Length(); i++) {
         dsReadDsData(stream, (*cur)[i]);
      }
      data.StoreValue(vec);
   } else if (pType == dsDATA_TYPE::GetType<dsVECTOR_PARAM_LIST>()) {
      int length;
      stream.Read(&length);

      dsVECTOR_PARAM_LIST list;
      for (int i = 0; i < length; i++) {
         dsSTRING id;
         stream.Read(id);
         dsDATA cur;
         dsReadDsData(stream, cur);
         list.Set(dsSTRID(id, FALSE), cur);
      }
      data.StoreValue(list);
   } else if (pType == dsDATA_TYPE::GetType<dsSTRID>()) {
      dsSTRING str;
      stream.Read(str);
      data.StoreValue(dsSTRID(str, FALSE));
   } else {
      data = dsDATA(pType);
      data.Load(&stream);
   }
}
